﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;

namespace Behaviorize.TextEditor.Coloring
{
	[Export(typeof(ITaggerProvider))]
	[ContentType("feature")]
	[TagType(typeof(ColorTag))]
	internal sealed class ColorTagProvider : ITaggerProvider
	{
		public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
		{
			return new ColorTagger(buffer) as ITagger<T>;
		}
	}

	public class ColorTag : ITag
	{
		public ColorTag(TokenType type)
		{
			Type = type;
		}

		public TokenType Type { get; private set; }
	}

	public class ColorTagger : ITagger<ColorTag>
	{
		private readonly ITextBuffer _buffer;
		private readonly Dictionary<string, TokenType> _tokens;
		private TagSpan<ColorTag> _multipleFeatureSpan; 

		public ColorTagger(ITextBuffer buffer)
		{
			_buffer = buffer;
			_tokens = new Dictionary<string, TokenType>
				{
					{"and", TokenType.And},
					{"description:", TokenType.Description},
					{"examples:", TokenType.Examples},
					{"feature:", TokenType.Feature},
					{"given", TokenType.Given},
					{"i want", TokenType.Goal},
					{"in order to", TokenType.Motivation},
					{"so that", TokenType.Motivation},
					{"as a", TokenType.Role},
					{"scenario:", TokenType.Scenario},
					{"then", TokenType.Then},
					{"when", TokenType.When}
				};

			Reparse();

			_buffer.Changed += (o, e) => Reparse();
		}

		private void Reparse()
		{
			_multipleFeatureSpan = null;
			ITextSnapshot snapshot = _buffer.CurrentSnapshot;
			int featuresFound = 0;

			foreach (ITextSnapshotLine snapshotLine in snapshot.Lines)
			{
				string lineOfText = snapshotLine.GetText().Trim();

				if (lineOfText.ToLowerInvariant().StartsWith("feature:")
					&& ++featuresFound > 1)
				{
					_multipleFeatureSpan = new TagSpan<ColorTag>(new SnapshotSpan(snapshotLine.Start, snapshot.Length - snapshotLine.Start), new ColorTag(TokenType.Erroneus));
					return;
				}
			}
		}

		public IEnumerable<ITagSpan<ColorTag>> GetTags(NormalizedSnapshotSpanCollection spans)
		{
			return GetColorTags(spans);
		}

		public IEnumerable<ITagSpan<ColorTag>> GetColorTags(IEnumerable<SnapshotSpan> snapshotSpans)
		{
			int endOfAllowedCode = _multipleFeatureSpan != null
				? _multipleFeatureSpan.Span.Start.Position
				: _buffer.CurrentSnapshot.Length;

			var tags = (snapshotSpans
				.TakeWhile(snapshotSpan => snapshotSpan.Start.Position < endOfAllowedCode)
				.SelectMany(span => GetTagsInLine(span.GetText().Trim(), span.Start))).ToList();

			if (_multipleFeatureSpan != null)
			{
				tags.Add(_multipleFeatureSpan);
			}

			return tags;
		}

		public IEnumerable<ITagSpan<ColorTag>> GetTagsInLine(string lineOfText, SnapshotPoint start)
		{
			// Tokens
			foreach (string token in _tokens.Keys)
			{
				if (lineOfText.ToLowerInvariant().StartsWith(token))
				{
					yield return new TagSpan<ColorTag>(new SnapshotSpan(start, token.Length), new ColorTag(_tokens[token]));
				}
			}

			// Example table pipes
			int pos = -1;

			while ((pos = lineOfText.IndexOf("|", pos + 1)) > -1)
			{
				yield return new TagSpan<ColorTag>(new SnapshotSpan(start + pos, 1), new ColorTag(TokenType.ExampleTablePipe));
			}

			// Example usages
			int usageStart = -1;
			while ((usageStart = lineOfText.IndexOf("<", usageStart + 1)) > -1)
			{
				using (StringReader reader = new StringReader(lineOfText.Substring(usageStart + 1)))
				{
					int length = 0;
					int ret;
					while ((ret = reader.Read()) > -1)
					{
						char c = (char)ret;

						if (c == '<')
						{
							usageStart--;
							break;
						}
						if (c == '>')
						{
							yield return new TagSpan<ColorTag>(new SnapshotSpan(start + usageStart, length + 2), new ColorTag(TokenType.ExampleUsage));
							break;
						}

						length++;
					}
				}
			}
		}

		public event EventHandler<SnapshotSpanEventArgs> TagsChanged
		{
			add { }
			remove { }
		}
	}
}